// for the license, see the end of the file
#pragma once

#include <map>
#include <set>
#include <functional>
#include <string>

#include <entt/entt.hpp>
#include <ImGui/imgui.h>
#include <USRefl/USRefl_99.h>
#include <enoki/array.h>
#include <RenderTaskPool/Component.h>
#include <ImGui/imgui_stdlib.h>
#include "../YDXRender/ResourcePool.h"
#include "../Awesome/Texture.h"
#include "../Awesome/AssetManager.h"
#include <Awesome/Payload.h>
#include "../Awesome/MaterialAsset.h"
#ifndef MM_IEEE_ASSERT
	#define MM_IEEE_ASSERT(x) assert(x)
#endif

#define MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY "MM_IEEE_ENTITY"

#ifndef MM_IEEE_ENTITY_WIDGET
	#define MM_IEEE_ENTITY_WIDGET ::MM::EntityWidget
#endif

// reflect tool
template<typename Instance, template<typename...> class T>
struct is_instance_of;
template<typename Instance, template<typename...> class T>
constexpr bool is_instance_of_v = is_instance_of<Instance, T>::value;
template<typename Instance,template<typename...> class T>
struct is_instance_of :std::false_type {};
template<typename Object,template<typename...> class T>
struct is_instance_of<T<Object>, T> :std::true_type {};

namespace MM {
	template <class EntityType>
	inline void EntityWidget(EntityType& e, entt::basic_registry<EntityType>& reg)
	{

		static EntityType m_selectEntity = (EntityType)-1;
		ImGui::PushID(static_cast<int>(entt::to_integral(e)));

		if (reg.valid(e)) {
			char label[32];
			sprintf(label, "ID: %d", entt::to_integral(e));
			ImGui::Bullet(); ImGui::Selectable(label, false);
			if (ImGui::IsItemActive())
				m_selectEntity = e;		
		}
		else if (m_selectEntity != (EntityType)-1) {
			e = m_selectEntity;
			ImGui::Text("ID: %d", entt::to_integral(e));
		}
		ImGui::PopID();
	}

	//template <class EntityType>
	//inline void EntityWidget(EntityType& e, entt::basic_registry<EntityType>& reg, bool dropTarget = false)
	//{
	//	ImGui::PushID(static_cast<int>(entt::to_integral(e)));
	//
	//	if (reg.valid(e)) {
	//		ImGui::Text("ID: %d", entt::to_integral(e));
	//	}
	//	else {
	//		ImGui::Text("Invalid Entity");
	//	}
	//
	//
	//	if (reg.valid(e)) {
	//		if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
	//			ImGui::SetDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY, &e, sizeof(e));
	//			ImGui::Text("ID: %d", entt::to_integral(e));
	//			ImGui::EndDragDropSource();
	//		}
	//	}
	//
	//	if (dropTarget && ImGui::BeginDragDropTarget()) {
	//		if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) {
	//			e = *(EntityType*)payload->Data;
	//		}
	//
	//		ImGui::EndDragDropTarget();
	//	}
	//
	//	ImGui::PopID();
	//}
    //

	template<typename Fld>
	constexpr std::string_view GetFieldName(Fld field) {
			return field.name;
	}
	// return true if something changed
	template<typename Field,typename Value, typename EntityType>
	void InspectVar(Field field, Value& var, entt::basic_registry<EntityType>& reg)
	{
		if constexpr (std::is_same_v<Value, bool>)
			ImGui::Checkbox(GetFieldName(field).data(), &var);
		else if constexpr (std::is_same_v<Value, uint8_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_U8, &var, 1.f);
		else if constexpr (std::is_same_v<Value, uint16_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_U16, &var, 1.f);
		else if constexpr (std::is_same_v<Value, uint32_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_U32, &var, 1.f);
		else if constexpr (std::is_same_v<Value, uint64_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_U64, &var, 1.f);
		else if constexpr (std::is_same_v<Value, int8_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_S8, &var, 1.f);
		else if constexpr (std::is_same_v<Value, int16_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_S16, &var, 1.f);
		else if constexpr (std::is_same_v<Value, int32_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_S32, &var, 1.f);
		else if constexpr (std::is_same_v<Value, int64_t>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_S64, &var, 1.f);
		else if constexpr (std::is_same_v<Value, float>)
		{
			ImGui::DragFloat(GetFieldName(field).data(), &var, 0.001f);
		}
		else if constexpr (std::is_same_v<Value, double>)
			ImGui::DragScalar(GetFieldName(field).data(), ImGuiDataType_Double, &var, 0.001f);
		else if constexpr (std::is_same_v<Value, std::string>)
			ImGui::InputText(GetFieldName(field).data(), &var);
		else if constexpr (std::is_same_v<Value, entt::entity>)
		{
			ImGui::Text("(*)");
			ImGui::SameLine();
			if (reg.valid(var))
			{
				auto name = reg.get<Component::Name>(var);
				ImGui::Button(name.value.c_str());
			}
			else
				ImGui::Button("None (Entity)");
		}
		else if constexpr (std::is_same_v<Value, Float3>)
		{
			ImGui::InputFloat3(GetFieldName(field).data(), var.data());
		}
		else if constexpr (is_instance_of_v<Value, std::shared_ptr>)
		{
			using Element = typename Value::element_type;
			static_assert(std::is_base_of_v<YXX_AS::AssetObject, Element>);
			ImGui::BeginGroup();
			{
				ImGui::Text("(*)");
				ImGui::SameLine();

				if (var != nullptr)
				{
					const auto& path = AssetManager::Get().Asset2Path(*var);
					ImGui::Button(path.stem().string().c_str());
				}
				else
					ImGui::Button("nullptr");
				if (auto tex2d = std::dynamic_pointer_cast<YXX_AS::Texture2D>(var); tex2d != nullptr)
				{
					ImGui::ImageButton((ImTextureID)YXX::DX11::ResourcePool::Get().GetTextureSRV(*tex2d), ImVec2{ 128,128 }, ImVec2(0, 0), ImVec2(1, 1), 0);
				}
			}
			ImGui::EndGroup();

			if (ImGui::BeginDragDropTarget())
			{
				if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(Payload::Guid))
				{
					assert(payload->DataSize == sizeof(xg::Guid));
					const auto& payload_guid = *(xg::Guid*)payload->Data;

					auto newVar = AssetManager::Get().Guid2Asset<Element>(payload_guid);
					if (newVar)
						var = newVar;
					else
						std::cout << "the asset differs with " << typeid(Element).name() << std::endl;
				}
				ImGui::EndDragDropTarget();
			}
		}
		else if constexpr (std::is_same_v<Value,YXX_AS::RS>)
		{
			//YXX_AS::RS var;

			int item_current_idx = (int)var;

			if (ImGui::BeginCombo("Rasterize State", Ubpa::USRefl::TypeInfo<YXX_AS::RS>::fields.NameOfValue(var).data()))
			{
				int n = -1;
				Ubpa::USRefl::TypeInfo<Value>::fields.ForEach([&item_current_idx,&n](auto field) {
					n++;

					const bool is_selected = (item_current_idx == n);
					if (ImGui::Selectable(field.name.data(), is_selected))
						item_current_idx = n;

					// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
					if (is_selected)
						ImGui::SetItemDefaultFocus();
					});
				ImGui::EndCombo();
			}

			var = (YXX_AS::RS)item_current_idx;
		}
		else 
			ImGui::Text(GetFieldName(field).data());
		
	}

template <class Comp, class EntityType>
void ComponentEditorWidget([[maybe_unused]] entt::basic_registry<EntityType>& registry, [[maybe_unused]] EntityType entity)
{
	auto& comp = registry.get<Comp>(entity);

	Ubpa::USRefl::TypeInfo<Comp>::ForEachVarOf(comp, [&](const auto& field, auto& var) {
		InspectVar(field, var, registry);
		});
}

template <class Component, class EntityType>
void ComponentAddAction(entt::basic_registry<EntityType>& registry, EntityType entity)
{
	registry.template emplace<Component>(entity);
}

template <class Component, class EntityType>
void ComponentRemoveAction(entt::basic_registry<EntityType>& registry, EntityType entity)
{
	registry.template remove<Component>(entity);
}

template <class EntityType = entt::entity>
class EntityEditor {
public:
	using Registry = entt::basic_registry<EntityType>;
	using ComponentTypeID = entt::id_type;

	struct ComponentInfo {
		using Callback = std::function<void(Registry&, EntityType)>;
		std::string name;
		Callback widget, create, destroy;
	};

	bool show_window = true;
private:
	

	std::map<ComponentTypeID, ComponentInfo> component_infos;

	bool entityHasComponent(Registry& registry, EntityType& entity, ComponentTypeID type_id)
	{
		ComponentTypeID type[] = { type_id };
		return registry.runtime_view(std::cbegin(type), std::cend(type)).contains(entity);
	}

public:
	template <class Component>
	ComponentInfo& registerComponent(const ComponentInfo& component_info)
	{
		auto index = entt::type_hash<Component>::value();
		auto insert_info = component_infos.insert_or_assign(index, component_info);
		MM_IEEE_ASSERT(insert_info.second);
		return std::get<ComponentInfo>(*insert_info.first);
	}

	template <class Component>
	ComponentInfo& registerComponent(const std::string& name, typename ComponentInfo::Callback widget)
	{
		return registerComponent<Component>(ComponentInfo{
			name,
			widget,
			ComponentAddAction<Component, EntityType>,
			ComponentRemoveAction<Component, EntityType>,
		});
	}

	template <class Component>
	ComponentInfo& registerComponent(const std::string& name)
	{
		return registerComponent<Component>(name, ComponentEditorWidget<Component, EntityType>);
	}

	void renderEditor(Registry& registry, EntityType e)
	{
		if (registry.valid(e)) {
			ImGui::PushID(static_cast<int>(entt::to_integral(e)));
			std::map<ComponentTypeID, ComponentInfo> has_not;
			for (auto& [component_type_id, ci] : component_infos) {
				if (entityHasComponent(registry, e, component_type_id)) {
					ImGui::PushID(component_type_id);
					if (ImGui::Button("-")) {
						ci.destroy(registry, e);
						ImGui::PopID();
						continue; // early out to prevent access to deleted data
					} else {
						ImGui::SameLine();
					}

					if (ImGui::CollapsingHeader(ci.name.c_str())) {
						ImGui::Indent(30.f);
						ImGui::PushID("Widget");
						ci.widget(registry, e);
						ImGui::PopID();
						ImGui::Unindent(30.f);
					}
					ImGui::PopID();
				} else {
					has_not[component_type_id] = ci;
				}
			}

			if (!has_not.empty()) {
				if (ImGui::Button("+ Add Component")) {
					ImGui::OpenPopup("Add Component");
				}

				if (ImGui::BeginPopup("Add Component")) {
					ImGui::TextUnformatted("Available:");
					ImGui::Separator();

					for (auto& [component_type_id, ci] : has_not) {
						ImGui::PushID(component_type_id);
						if (ImGui::Selectable(ci.name.c_str())) {
							ci.create(registry, e);
						}
						ImGui::PopID();
					}
					ImGui::EndPopup();
				}
			}
			ImGui::PopID();
		}
	}

	void renderEntityList(Registry& registry)
	{
		static std::set<ComponentTypeID> comp_list;
		ImGui::Text("Components Filter:");
		ImGui::SameLine();
		if (ImGui::SmallButton("clear")) {
			comp_list.clear();
		}

		ImGui::Indent();

		for (const auto& [component_type_id, ci] : component_infos) {
			bool is_in_list = comp_list.count(component_type_id);
			bool active = is_in_list;

			ImGui::Checkbox(ci.name.c_str(), &active);

			if (is_in_list && !active) { // remove
				comp_list.erase(component_type_id);
			} else if (!is_in_list && active) { // add
				comp_list.emplace(component_type_id);
			}
		}

		ImGui::Unindent();
		ImGui::Separator();

		if (comp_list.empty()) {
			ImGui::Text("Orphans:");
			registry.orphans([&registry](auto e){
				MM_IEEE_ENTITY_WIDGET(e, registry);
			});
		} else {
			auto view = registry.runtime_view(comp_list.begin(), comp_list.end());
			ImGui::Text("%lu Entities Matching:", view.size_hint());

			if (ImGui::BeginChild("entity list")) {
				for (auto e : view) {
					MM_IEEE_ENTITY_WIDGET(e, registry);
				}
			}
			ImGui::EndChild();
		}
	}

	[[deprecated("Use renderEditor() instead. And manage the window yourself.")]]
	void render(Registry& registry, EntityType& e)
	{
		if (show_window) {
			if (ImGui::Begin("Entity Editor", &show_window)) {
				renderEditor(registry, e);
			}
			ImGui::End();
		}
	}

	
	// displays both, editor and list
	// uses static internally, use only as a quick way to get going!
	void renderSimpleCombo(Registry& registry, EntityType& e)
	{
		if (ImGui::Begin("Entities"))
		{
			renderEntityList(registry);

			ImGui::End();
		}

		if (ImGui::Begin("Inspector"))
		{
			renderEditor(registry, e);
			ImGui::End();
		}
	}

};

} // MM

// MIT License

// Copyright (c) 2019-2021 Erik Scholz
// Copyright (c) 2020 Gnik Droy

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

